/* 
 * File:   conf_parser.h
 * Author: liujianping@snda.com
 * description:
 * 1. configure format: key=value
 * 2. value format:
 *      int, float, long
 *      string
 *      string with delimeters, like "<space><;><:><,>"
 * 
 * history:
 * Created on 2011年3月21日, 下午5:08
 */

#ifndef CONF_PARSER_H
#define	CONF_PARSER_H

#include <ctype.h>
#include <stdio.h>

#include <string>
#include <vector>
#include <map>
using namespace std;

#include "log.h"
namespace edm
{
    namespace common
    {
    
#define ISASCII(c)	isascii(_UCHAR_(c))
#define _UCHAR_(c)	((unsigned char)(c))
#define ISALNUM(c)	(ISASCII(c) && isalnum((unsigned char)(c)))
#define ISALPHA(c)	(ISASCII(c) && isalpha((unsigned char)(c)))
#define ISCNTRL(c)	(ISASCII(c) && iscntrl((unsigned char)(c)))
#define ISDIGIT(c)	(ISASCII(c) && isdigit((unsigned char)(c)))
#define ISGRAPH(c)	(ISASCII(c) && isgraph((unsigned char)(c)))
#define ISLOWER(c)	(ISASCII(c) && islower((unsigned char)(c)))
#define ISPRINT(c)	(ISASCII(c) && isprint((unsigned char)(c)))
#define ISPUNCT(c)	(ISASCII(c) && ispunct((unsigned char)(c)))
#define ISSPACE(c)	(ISASCII(c) && isspace((unsigned char)(c)))
#define ISUPPER(c)	(ISASCII(c) && isupper((unsigned char)(c)))
#define TOLOWER(c)	(ISUPPER(c) ? tolower((unsigned char)(c)) : (c))
#define TOUPPER(c)	(ISLOWER(c) ? toupper((unsigned char)(c)) : (c))

static const char *split_nameval(char *buf, char **name, char **value, char delc)
{
    char   *np;				/* name substring */
    char   *vp;				/* value substring */
    char   *cp;
    char   *ep;

    /*
     * Ugly macros to make complex expressions less unreadable.
     */
#define SKIP(start, var, cond) \
	for (var = start; *var && (cond); var++);

#define TRIM(s) { \
	char *p; \
	for (p = (s) + strlen(s); p > (s) && ISSPACE(p[-1]); p--); \
	*p = 0; \
    }

    SKIP(buf, np, ISSPACE(*np));		/* find name begin */
    if (*np == 0)
	return ("missing attribute name");
    
    if (*np == '#')
        return ("comment line");
    
    SKIP(np, ep, !ISSPACE(*ep) && *ep != delc);	/* find name end */
    SKIP(ep, cp, ISSPACE(*cp));			/* skip blanks before '=' */
    if (*cp != delc)				/* need '=' */
	return ("missing '=' after attribute name");
    *ep = 0;					/* terminate name */
    cp++;					/* skip over '=' */
    SKIP(cp, vp, ISSPACE(*vp));			/* skip leading blanks */
    TRIM(vp);					/* trim trailing blanks */
    *name = np;
    *value = vp;
    return (0);
}

class conf_parser_t
{
public: 
    //! constructor
	conf_parser_t(char delc = '='):
		m_delchar(delc),m_opened(false)
	{
		logtrace((COMMON, "conf_parser_t::conf_parser_t"));
	}
    
    //! destructor
	~conf_parser_t()
	{
		logtrace((COMMON, "conf_parser_t::~conf_parser_t begin ..."));

		if(m_opened) close();

		logtrace((COMMON, "conf_parser_t::~conf_parser_t end ok."));
	}
    
private:
    conf_parser_t(conf_parser_t& conf_parser);
    conf_parser_t& operator=(conf_parser_t& conf_parser);
    
    //! parse configure file
    const char* parse_i();
public:
    //! open conf parser, will read the configure file content
    int open(const char* file);
    
    //! close conf parser
    void close();
    
    //! reload configure
    int reload();
    
    //! get interger configure value
    int get_int_value(const char* key, int& value, int default_value);
    
    //! get long configure value
    int get_long_value(const char* key, long& value, long default_value);
    
    //! get float configure value
    int get_double_value(const char* key, double& value, double default_value);
    
    //! get bool configure value
    int get_bool_value(const char* key, bool& value, bool default_value);
    
    //! get string configure value
    int get_string_value(const char* key, string& value, string default_value);
      
    //! get vector configure value, only support string
    int get_vector_value(const char* key, vector<string>& values);
    
private:
    char                m_delchar;
    bool                m_opened;
    string              m_file_path;
    map<string, string> m_confs;
    
};

//! parse configure file
inline const char* conf_parser_t::parse_i()
{
    logtrace((COMMON, "conf_parser_t::parse_i begin ..."));
    
    if(m_file_path.empty())
    {
        logerror((COMMON, "conf_parser_t::parse_i file path empty"));
        return ("file path is empty");
    }
    
    if(::access(m_file_path.c_str(), F_OK)) return ("configure file not exist");

    FILE* fp = ::fopen(m_file_path.c_str(), "r");
    if(!fp) return ("configure file open failed");
        
    char line[1024];
    while(fgets(line, 1024, fp))
    {
        char *name, *value;
        if(split_nameval(line, &name, &value, m_delchar)) continue;
        
        string key(name);
        map<string, string>::iterator it = m_confs.find(key);
        if(it != m_confs.end())
        {
            m_confs[key] = m_confs[key] + " ";
            m_confs[key] = m_confs[key] + value;
            continue;
        }
        
        m_confs[key] = value;
    }
      
    ::fclose(fp);
    logtrace((COMMON, "conf_parser_t::parse_i end ok."));
    return 0;
}

//! open conf parser, will read the configure file content
inline int conf_parser_t::open(const char* file)
{
    logtrace((COMMON, "conf_parser_t::open begin ..."));
    
    if(m_opened)
    {
        logwarn((COMMON, "conf_parser_t::open already opened"));
        return 0;
    }

    m_file_path.assign(file);
    //! parse configure file
    const char* retcode = parse_i();
    if(retcode)
    {
        logerror((COMMON, "conf_parser_t::open => parse_i failed:<%s>", retcode));
        return -1;
    }
    
    m_opened = true;
    logtrace((COMMON, "conf_parser_t::open end ok."));
    return 0;
}

//! close conf parser
inline void conf_parser_t::close()
{
    logtrace((COMMON, "conf_parser_t::close begin ..."));
    
    if(!m_opened)
    {
        logwarn((COMMON, "conf_parser_t::open already closed"));
        return;
    }

    m_confs.clear();  
    m_opened = false;
    logtrace((COMMON, "conf_parser_t::close end ok."));
    return;    
}

//! reload configure
inline int conf_parser_t::reload()
{
    logtrace((COMMON, "conf_parser_t::reload begin ..."));
    
    close();
    if(open(m_file_path.c_str()))
    {
        logerror((COMMON, "conf_parser_t::reload => open failed."));
        return -1;
    }
    
    logtrace((COMMON, "conf_parser_t::reload end ok."));
    return 0;    

}

//! get interger configure value
inline int conf_parser_t::get_int_value(const char* key, int& value, int default_value)
{
    logtrace((COMMON, "conf_parser_t::get_int_value begin ..."));
    
    if(!m_opened)
    {
        logerror((COMMON, "conf_parser_t::get_int_value parser not opened."));
        return -1;
    }

    string skey(key);
    map<string, string>::iterator it = m_confs.find(skey);
    if(it == m_confs.end())
    {
        logwarn((COMMON, "conf_parser_t::get_int_value key not find, use default value:<%d>.", 
                default_value));
        value = default_value;
        return 0;        
    }
    
    value = atoi(it->second.c_str());
    logtrace((COMMON, "conf_parser_t::get_int_value <%s:%d> end ok.", key, value));    
    return 0;
}

//! get long configure value
inline int conf_parser_t::get_long_value(const char* key, long& value, long default_value)
{
    logtrace((COMMON, "conf_parser_t::get_long_value begin ..."));
    
    if(!m_opened)
    {
        logerror((COMMON, "conf_parser_t::get_long_value parser not opened."));
        return -1;
    }

    string skey(key);
    map<string, string>::iterator it = m_confs.find(skey);
    if(it == m_confs.end())
    {
        logwarn((COMMON, "conf_parser_t::get_long_value key not find, use default value:<%d>.", 
                default_value));
        value = default_value;
        return 0;        
    }
    
    value = atol(it->second.c_str());
    logtrace((COMMON, "conf_parser_t::get_long_value <%s:%ld> end ok.", key, value));    
    return 0;
}

//! get float configure value
inline int conf_parser_t::get_double_value(const char* key, double& value, double default_value)
{
    logtrace((COMMON, "conf_parser_t::get_double_value begin ..."));
    
    if(!m_opened)
    {
        logerror((COMMON, "conf_parser_t::get_double_value parser not opened."));
        return -1;
    }

    string skey(key);
    map<string, string>::iterator it = m_confs.find(skey);
    if(it == m_confs.end())
    {
        logwarn((COMMON, "conf_parser_t::get_double_value key not find, use default value:<%d>.", 
                default_value));
        value = default_value;
        return 0;        
    }
    
    value = atof(it->second.c_str());
    logtrace((COMMON, "conf_parser_t::get_double_value <%s:%f> end ok.", key, value));    
    return 0;
}

//! get bool configure value
inline int conf_parser_t::get_bool_value(const char* key, bool& value, bool default_value)
{
    logtrace((COMMON, "conf_parser_t::get_bool_value begin ..."));
    
    if(!m_opened)
    {
        logerror((COMMON, "conf_parser_t::get_bool_value parser not opened."));
        return -1;
    }

    string skey(key);
    map<string, string>::iterator it = m_confs.find(skey);
    if(it == m_confs.end())
    {
        logwarn((COMMON, "conf_parser_t::get_bool_value key not find, use default value:<%d>.", 
                default_value));
        value = default_value;
        return 0;        
    }
    
    value = false;
    if(strcasecmp(it->second.c_str(),"true") == 0)
        value = true;
    logtrace((COMMON, "conf_parser_t::get_bool_value <%s:%s> end ok.", key, (value ? "true" : "false")));    
    return 0;
}

//! get string configure value
inline int conf_parser_t::get_string_value(const char* key, string& value, string default_value)
{
    logtrace((COMMON, "conf_parser_t::get_string_value begin ..."));
    
    if(!m_opened)
    {
        logerror((COMMON, "conf_parser_t::get_string_value parser not opened."));
        return -1;
    }

    string skey(key);
    map<string, string>::iterator it = m_confs.find(skey);
    if(it == m_confs.end())
    {
        logwarn((COMMON, "conf_parser_t::get_string_value key not find, use default value:<%s>.", 
                default_value.c_str()));
        value = default_value;
        return 0;        
    }
    
    value = it->second;
    logtrace((COMMON, "conf_parser_t::get_string_value <%s:%s> end ok.", key, value.c_str()));    
    return 0;
}

//! get vector configure value, only support string
inline int conf_parser_t::get_vector_value(const char* key, vector<string>& values)
{
    logtrace((COMMON, "conf_parser_t::get_vector_value begin ..."));
    
    if(!m_opened)
    {
        logwarn((COMMON, "conf_parser_t::get_vector_value already closed"));
        return 0;
    }

    string skey(key);
    map<string, string>::iterator it = m_confs.find(skey);
    if(it == m_confs.end())
    {
        logerror((COMMON, "conf_parser_t::get_vector_value no key named:<%s>", key));
        return -1;
    }
    
    char* saveptr;
    char *p, *token;
    for(p = (char*)it->second.c_str(); ;p = NULL)
    {
        token = strtok_r(p, ",; ", &saveptr);
        if(token == NULL) break;

        values.push_back(token);
    }
    
    logtrace((COMMON, "conf_parser_t::get_vector_value end ok."));
    return 0;
}

    }
}

#endif	/* CONF_PARSER_H */

